<?php
/**
 * Api.php
 *
 * Api模块 （处理需要access_token的主动接口）
 * 
 * 
 * - 用户管理（获取用户基本信息、获取用户列表、网页授权获取用户基本信息）
 
 *
 * Class Api
 * 
 */
 
require_once "Utils/HttpCurl.class.php";
require_once "Utils/SHA1.class.php";
require_once "Utils/Xml.class.php";

class Api 
{
    // 微信API域名
    const API_DOMAIN = 'https://api.weixin.qq.com/';
    //页面授权
    const SNSAPI_BASE = "snsapi_base";
    const SNSAPI_USERINFO = "snsapi_userinfo";

    // 开发者中心-配置项-AppID(应用ID)
    protected $appId;
    // 开发者中心-配置项-AppSecret(应用密钥)
    protected $appSecret;
    // 微信支付商户号，商户申请微信支付后，由微信支付分配的商户收款账号
    protected $mchId;
    // API密钥,微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
    protected $key;

    /** @var callable $get_access_token_diy 用户自定义获取access_token的方法 */
    protected $get_access_token_diy;
    /** @var callable $save_access_token_diy 用户自定义保存access_token的方法 */
    protected $save_access_token_diy;

    /** @var callable $get_jsapi_ticket_diy 用户自定义获取jsapi_ticket的方法 */
    protected $get_jsapi_ticket_diy;
    /** @var callable $save_jsapi_ticket_diy 用户自定义保存jsapi_ticket的方法 */
    protected $save_jsapi_ticket_diy;

    /**
     * 设定配置项
     *
     * @param array $config
     */
    public function __construct($config)
    {
        $this->appId                    =   $config['appId'];
        $this->appSecret                =   $config['appSecret'];
        $this->mchId                    =   isset($config['mchId']) ? $config['mchId'] : false;
        $this->key                      =   isset($config['key']) ? $config['key'] : false;
        $this->get_access_token_diy     =   isset($config['get_access_token']) ? $config['get_access_token'] : false;
        $this->save_access_token_diy    =   isset($config['save_access_token']) ? $config['save_access_token'] : false;
        $this->get_jsapi_ticket_diy     =   isset($config['get_jsapi_ticket']) ? $config['get_jsapi_ticket'] : false;
        $this->save_jsapi_ticket_diy    =   isset($config['save_jsapi_ticket']) ? $config['save_jsapi_ticket'] : false;
    }

    /**
     * 校验access_token是否过期
     *
     * @param string $token
     *
     * @return bool
     */
    public function valid_access_token($token)
    {
        return $token && isset($token->expires_in) && ($token->expires_in > time() + 1200);
    }

    /**
     * 生成新的access_token
     *
     * @return mixed
     */
    public function new_access_token()
    {
        $url = self::API_DOMAIN . 'cgi-bin/token?grant_type=client_credential&appid=' . $this->appId . '&secret=' . $this->appSecret;
        $res = HttpCurl::get($url, 'json');

        // 异常处理: 获取access_token网络错误
        if ($res === false) {
            @error_log('Http Get AccessToken Error.', 0);
            return false;            
        }

        // 异常处理: access_token获取失败
        if (!isset($res->access_token)) {
            @error_log('Get AccessToken Error: ' . json_encode($res), 0);
            return false;
        }
        $res->expires_in += time();
        return $res;
    }

    /**
     * 获取access_token
     *
     * @return string
     */
    public function get_access_token()
    {
        $token = false;
        if ($this->get_access_token_diy !== false) {
            // 调用用户自定义获取AccessToken方法
            $token = call_user_func($this->get_access_token_diy);
            if ($token) {
                $token = json_decode($token);
            }
        } else {
            // 异常处理: 获取access_token方法未定义
            @error_log('Not set get_tokenDiy method, AccessToken will be refreshed each time.', 0);
        }
        // 验证AccessToken是否有效        
        if (!$this->valid_access_token($token)) {

            // 生成新的AccessToken
            $token = $this->new_access_token();
            if ($token === false) {
                return false;
            }

            // 保存新生成的AccessToken
            if ($this->save_access_token_diy !== false) {
                // 用户自定义保存AccessToken方法    
                call_user_func($this->save_access_token_diy, json_encode($token));
            } else {
                // 异常处理: 保存access_token方法未定义
                @error_log('Not set saveTokenDiy method, AccessToken will be refreshed each time.', 0);
            }
        }
        return $token->access_token;
    }
    
   
   

    

    /**
     * 获取用户基本信息
     *
     * Examples:
     * ```
     * $api->get_user_info('ocNtAt_K8nRlAdmNEo_R0WVg_rRw');
     * $api->get_user_info('ocNtAt_K8nRlAdmNEo_R0WVg_rRw', 'zh_TW');
     * ```
     * Result:
     * ```
     * [
     *     null,
     *     {
     *         subscribe: 1,
     *         openid: "ocNtAt_K8nRlAdmNEo_R0WVg_rRw",
     *         nickname: "赵利明",
     *         sex: 1,
     *         language: "zh_CN",
     *         city: "浦東新區",
     *         province: "上海",
     *         country: "中國",
     *         headimgurl: "http://wx.qlogo.cn/mmopen/eFIz8Uk9INlmmw4dAblRbUxIhjoJtPUUGGJXaWp6rd48v4vUMhmk7GvfNv2Kd0xSvRWfMk7PnOIoicz3ibMf38zvWnr7bCXNZC/0",
     *         subscribe_time: 1440150875,
     *         remark: "",
     *         groupid: 100
     *     }
     * ]
     * ```
     *
     * @param string $open_id [普通用户的标识，对当前公众号唯一]
     * @param string $lang [可选：返回国家地区语言版本，zh_CN 简体，zh_TW 繁体，en 英语]
     *
     * @return array(err, data)
     * - `err`, 调用失败时得到的异常
     * - `res`, 调用正常时得到的对象
     */
    public function get_user_info($open_id, $lang = '')
    {
        if ($lang != '') {
            $lang = '&lang=' . $lang;
        }
        $url = self::API_DOMAIN . 'cgi-bin/user/info?access_token=' . $this->get_access_token() . '&openid=' . $open_id . $lang;
        $res = HttpCurl::get($url, 'json');
        // 异常处理: 获取时网络错误
        if ($res === false) {
            return Error::code('ERR_POST');
        }
        // 判断是否调用成功
        if (isset($res->openid)) {
            return array(null, $res);
        } else {
            return array($res, null);
        }
    }

    
    /**
     * 得到获取用户授权需要打开的页面链接
     *
     * !!! 跳转后若提示`微信redirect_uri参数错误`
     * 很大可能是微信号的 `网页授权获取用户基本信息` 无权限，或 `授权回调页面域名` 填写不正确
     *
     * Examples:
     * ```
     * $api->get_authorize_url('snsapi_base', 'http://wx.diary8.com/demo/snsapi/callback_snsapi_base.php');
     * $api->get_authorize_url('snsapi_userinfo', 'http://wx.diary8.com/demo/snsapi/callback_snsapi_userinfo.php');
     * ```
     *
     * @param string $scope 应用授权作用域
     *  `snsapi_base` 不弹出授权页面，直接跳转，只能获取用户openid
     *  `snsapi_userinfo` 弹出授权页面，可通过openid拿到昵称、性别、所在地。即使在未关注的情况下，只要用户授权，也能获取其信息
     * @param string $redirect_uri 授权后要跳转到的地址
     * @param string $state 非必须, 重定向后会带上state参数，开发者可以填写a-zA-Z0-9的参数值，最多128字节
     *
     * @return string
     */
    public function get_authorize_url($scope, $redirect_uri, $state = '')
    {
        $redirect_uri = urlencode($redirect_uri);
        $url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=' . $this->appId .
            '&redirect_uri=' . $redirect_uri . '&response_type=code&scope=' . $scope .
            '&state=' . $state . '#wechat_redirect';
        return $url;
    }

    /**
     * 获取用户授权后回调页面根据获取到的code，获取用户信息
     * 注：本函数将获取access_token和拉取用户信息集成在了一起，未对获取到的access_token进行保存
     *
     * Examples:
     * ```
     * $api->get_userinfo_by_authorize('snsapi_base', $_GET['code']);
     * $api->get_userinfo_by_authorize('snsapi_userinfo', $_GET['code']);
     * ```
     *
     * @param $scope `get_authorize_url`时使用的授权类型
     * @param string $lang 可选，返回国家地区语言版本，zh_CN 简体，zh_TW 繁体，en 英语
     *
     * @return array|object
     */
    public function get_userinfo_by_authorize($scope,$code,$lang = 'zh_CN')
    {
         // 1. 通过code换取网页授权access_token
            $url = self::API_DOMAIN . 'sns/oauth2/access_token?appid=' . $this->appId . '&secret=' . $this->appSecret .
                '&code=' . $code . '&grant_type=authorization_code';
            $res = HttpCurl::get($url, 'json');
            // 异常处理: 获取时网络错误
            if ($res === false) {
                return Error::code('ERR_POST');
            }
            // 判断是否调用成功
            if (isset($res->access_token)) {
                if ($scope == 'snsapi_userinfo') {
                    // 2.1 `snsapi_userinfo` 继续通过access_token和openid拉取用户信息
                    $url = self::API_DOMAIN . 'sns/userinfo?access_token=' . $res->access_token .
                        '&openid=' . $res->openid . '&lang=' . $lang;
                    $res = HttpCurl::get($url, 'json');
                    // 异常处理: 获取时网络错误
                    if ($res === false) {
                        return Error::code('ERR_POST');
                    }
                    // 判断是否调用成功
                    if (isset($res->openid)) {
                        return array(null, $res);
                    } else {
                        return array($res, null);
                    }
                } else {
                    // 2.2 `snsapi_base` 不弹出授权页面，直接跳转，只能获取用户openid
                    return array(null, $res);
                }
            } else {
                return array($res, null);
            }
    }

    /**
     * JS-SDK 生成一个新的jsapi_ticket
     *
     * @return mixed
     */
    public function new_jsapi_ticket()
    {
        $url = self::API_DOMAIN . 'cgi-bin/ticket/getticket?access_token=' . $this->get_access_token() . '&type=jsapi';
        $res = HttpCurl::get($url, 'json');
        // 异常处理: 获取时网络错误
        if ($res === false) {
            return Error::code('ERR_GET');
        }
        // 判断是否调用成功
        if ($res->errcode == 0) {
            return (object)array(
                'ticket' => $res->ticket,
                'expires_in' => $res->expires_in + time()
            );
        } else {
            return false;
        }
    }

    /**
     * JS-SDK 校验jsapi_ticket是否过期
     *
     * @param object $ticket
     * @return bool
     */
    public function valid_jsapi_ticket($ticket)
    {
        return $ticket && isset($ticket->expires_in) && ($ticket->expires_in > time() + 1200);
    }

    /**
     * JS-SDK 获取jsapi_ticket
     *
     * @return string $ticket
     */
    public function get_jsapi_ticket()
    {
        $ticket = false;
        if ($this->get_jsapi_ticket_diy !== false) {
            // 调用用户自定义获取jsapi_ticket方法
            $ticket = call_user_func($this->get_jsapi_ticket_diy);
            if ($ticket) {
                $ticket = json_decode($ticket);
            }
        } else {
            // 异常处理: 获取jsapi_ticket方法未定义
            @error_log('Not set getTicketDiy method, jsapi_ticket will be refreshed each time.', 0);
        }

        // 验证jsapi_ticket是否有效
        if (!$this->valid_jsapi_ticket($ticket)) {

            // 生成新的jsapi_ticket
            $ticket = $this->new_jsapi_ticket();
            if ($ticket === false) {
                return false;
            }

            // 保存新生成的AccessToken
            if ($this->save_jsapi_ticket_diy !== false) {
                // 用户自定义保存AccessToken方法
                call_user_func($this->save_jsapi_ticket_diy, json_encode($ticket));
            } else {
                // 异常处理: 保存access_token方法未定义
                @error_log('Not set saveTokenDiy method, jsapi_ticket will be refreshed each time.', 0);
            }
        }
        return $ticket->ticket;
    }

    /**
     * JS-SDK 获取JS-SDK配置需要的信息
     *
     * @param string $url 可选：调取JS-SDK的页面url，默认为HTTP_REFERER
     * @param string $type 可选：返回配置信息的格式 json & jsonp, 默认为对象数组
     * @param string $$jsonp_callback 可选：使用json的callback名称
     *
     * @return mixed
     *
     * Examples:
     * ```
     * $api->get_jsapi_config();
     * $api->get_jsapi_config('http://www.baidu.com/');
     * ```
     * Result:
     * ```
     * {
     *      errcode: 0,
     *      appId: "wx733d7f24bd29224a",
     *      timestamp: 1440073485,
     *      nonceStr: "5Ars5fLaLuPEXSgm",
     *      signature: "7f830aff99ff11fa931cae61b5b932b1f2c8ee10",
     *      url: "http://www.baidu.com/"
     * }
     * ```
     *
     * Examples:
     * ```
     * $api->get_jsapi_config('', 'json');
     * ```
     * Result:
     * ```
     * {"errcode":0,"appId":"wx733d7f24bd29224a","timestamp":1440073708,"nonceStr":"caFkkXnOhVrcq3Ke","signature":"1c6c08ddf6e0e3c0fd33aafcb160a9f67d6b8f94","url":null}
     * ```
     *
     * Examples:
     * ```
     * $api->get_jsapi_config('', 'jsonp');
     * $api->get_jsapi_config('', 'jsonp', 'callback');
     * ```
     * Result:
     * ```
     * ;jQuery17105012127514928579_1440073858610({"errcode":0,"appId":"wx733d7f24bd29224a","timestamp":1440073875,"nonceStr":"vsGBSM0MMiWeIJFQ","signature":"616005786e404fe0da226a6decc2730624bedbfc","url":null})
     * ```
     */
    public function get_jsapi_config($url = '', $type = '', $jsonp_callback = 'callback')
    {
        $jsapi_ticket = $this->get_jsapi_ticket();
        $nonce_str = SHA1::get_random_str();
        $timestamp = time();
        if ($url == '') {
            $url = $_SERVER['HTTP_REFERER'];
        }
        $signature = SHA1::get_jsapi_signature($jsapi_ticket, $nonce_str, $timestamp, $url);

        if ($signature === false) {
            $jsapi_config = array(
                'errcode' => -1,
                'errmsg' => 'get jsapi signature error.'
            );
        } else {
            $jsapi_config = array(
                'errcode' => 0,
                'appId' => $this->appId,
                'timestamp' => $timestamp,
                'nonceStr' => $nonce_str,
                'signature' => $signature,
                'url' => $url
            );
        }
        if ($type == 'json' || $type == 'jsonp') {
            $jsapi_config = json_encode($jsapi_config);
            if ($type == 'jsonp' && isset($_REQUEST[$jsonp_callback]) && !empty($_REQUEST[$jsonp_callback])) {
                $jsapi_config = ';' . $_REQUEST[$jsonp_callback] . '(' . $jsapi_config . ')';
            }
        }
        return $jsapi_config;
    }

    /**
     * 生成带参数的二维码
     *
     * @int $scene_id 场景值ID，临时二维码时为32位非0整型，永久二维码时最大值为100000（目前参数只支持1--100000）
     * @int $expire_seconds 可选：该二维码有效时间，以秒为单位。 最大不超过604800（即7天），默认为永久二维码，填写该项为临时二维码
     *
     * @return array(err, data)
     * - `err`, 调用失败时得到的异常
     * - `res`, 调用正常时得到的对象
     *
     * Examples:
     * ```
     * list($err, $data) = $api->create_qrcode(1234); // 创建一个永久二维码
     * list($err, $data) = $api->create_qrcode(1234, 100); //创建一个临时二维码，有效期100秒
     * ```
     * Result:
     * ```
     * [
     *  null,
     *  {
     *      ticket: "gQFM8DoAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xLzlVeU83dGZsMXNldlAtQ0hmbUswAAIEQcrVVQMEZAAAAA==",
     *      expire_seconds: 100,
     *      url: "http://weixin.qq.com/q/9UyO7tfl1sevP-CHfmK0"
     *  }
     * ]
     * ```
     */
    public function create_qrcode($scene_id, $expire_seconds = 0)
    {
        $url = self::API_DOMAIN . 'cgi-bin/qrcode/create?access_token=' . $this->get_access_token();
        $expire = $expire_seconds == 0 ? '' : '"expire_seconds": ' . $expire_seconds . ',';
        $action_name = $expire_seconds == 0 ? 'QR_LIMIT_SCENE' : 'QR_SCENE';
        $xml = sprintf('{%s"action_name": "%s", "action_info": {"scene": {"scene_id": %s}}}',
            $expire,
            $action_name,
            $scene_id);
        $res = HttpCurl::post($url, $xml, 'json');
        // 异常处理: 获取时网络错误
        if ($res === false) {
            return Error::code('ERR_GET');
        }
        // 判断是否调用成功
        if (isset($res->ticket)) {
            return array(null, $res);
        } else {
            return array($res, null);
        }
    }

    /**
     * 通过ticket换取二维码，返回二维码url地址
     *
     * @string $ticket 二维码的ticket
     *
     * @return string 二维码的url地址
     *
     * Examples:
     * ```
     * echo $api->get_qrcode_url('gQH58DoAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xLzQweUctT2psME1lcEJPYWJkbUswAAIEApzVVQMEZAAAAA==');
     * ```
     * Result:
     * ```
     * https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=gQH58DoAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xLzQweUctT2psME1lcEJPYWJkbUswAAIEApzVVQMEZAAAAA==
     * ```
     */
    public function get_qrcode_url($ticket)
    {
        return 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=' . $ticket;
    }

    /**
     * 通过ticket换取二维码，返回二维码图片的内容
     *
     * @string $ticket [获取到的二维码ticket]
     *
     * @return string [二维码图片的内容]
     *
     * Examples:
     * ```
     * list($err, $data) = $api->get_qrcode('gQGa8ToAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xLzlVeXJZWS1seGNlODZ2SV9XMkMwAAIEo5rVVQMEAAAAAA==');
     * header('Content-type: image/jpg');
     * echo $data;
     * ```
     */
    public function get_qrcode($ticket)
    {
        $url = self::get_qrcode_url($ticket);
        $res = HttpCurl::get($url);
        // 异常处理: 获取时网络错误
        if ($res === false) {
            return Error::code('ERR_GET');
        }
        return array(null, $res);
    }

    /**
 *  微信小程序 通过用户 授权获取到的code，获取session_key openid
 *
*/
   public function get_min_program_session_by_authorize($code,$lang = 'zh_CN')
   {
         $url = self::API_DOMAIN . 'sns/jscode2session?appid=' . $this->appId . '&secret=' . $this->appSecret .
                        '&js_code=' . $code . '&grant_type=authorization_code';

      // return $url;
        $res = HttpCurl::get($url, 'json');
        // 异常处理: 获取时网络错误
        if ($res === false) {
            return Error::code('ERR_POST');
        }
         // 判断是否调用成功
        if (isset($res->openid)) {
            return array(null, $res);
        } else {
            return array($res, null);
        }

   }

    
}
